package com.quanzhi.hyperscan.mshttp;

import com.quanzhi.hyperscan.wrapper.*;
import com.quanzhi.hyperscan.wrapper.Scanner;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConstructRegex {

    private static final Logger logger = LoggerFactory.getLogger(ConstructRegex.class);


    private LabelRule labelAPPRule = null;
    private LabelRule labelAPIRule = null;

    private LinkedList<Expression> httpAPPExpressions = new LinkedList<Expression>();
    private Scanner httpAPPScanner = null;
    private Database httpAPPDB = null;

    private LinkedList<Expression> httpAPIExpressions = new LinkedList<Expression>();
    private Scanner httpAPIScanner = null;
    private Database httpAPIDB = null;

    /**
     * @param resourceType httpapp or httpapi
     * @param ruleList     Rule:id(int) string(expression)
     * @return true or false
     */

    public Boolean init(String resourceType, List<Rule> ruleList) {
        if (ruleList.isEmpty() | ruleList.size() <= 0) {
            logger.warn("ruleList is empty,please check the ruleList");
            return false;
        }

        if (resourceType.toLowerCase().contains("httpapp")) {
            return initHttpAPP(ruleList);


        }
        if (resourceType.toLowerCase().contains("httpapi")) {
            return initHttpAPI(ruleList);

        }

        return false;
    }

    private synchronized Boolean initHttpAPP(List<Rule> ruleList) {
        if (httpAPPExpressions.size() > 0)
            httpAPPExpressions.clear();

        if (httpAPPDB != null) {
            try {
                httpAPPDB.close();
            } catch (IOException ioException) {
                logger.error("httpAPPDB close fail,err msg:" + ioException.getMessage());
                return false;
            }
        }

        for (int i = 0; i < ruleList.size(); i++) {
            String expr = ruleList.get(i).expr.toLowerCase();

            String type = new String();
            String outRule = new String();
            String outURL = new String();
            if (expr.contains("$(string)"))//&& !expr.contains(":")
            {
                type = "merge";
                outRule = ruleList.get(i).expr.replaceAll("\\$\\(string\\)", "[a-zA-Z1-9]+");
                outURL = ruleList.get(i).expr.replaceAll("http://", "httpapp:");
                if (expr.contains(":$(string)")) {
                    outURL = ruleList.get(i).expr.replaceAll(":\\$\\(string\\)", "");
                }

            }
            if (expr.contains("$[") && expr.contains("]")) {
                type = "merge";
                outRule = ruleList.get(i).expr.replaceAll("\\$\\[", "[");
                outURL = ruleList.get(i).expr.replaceAll("http://", "httpapp:");
            }
            if (expr.contains("#(string)")) {
                type = "split";
                outRule = ruleList.get(i).expr.replaceAll("\\#\\(string\\)", "[a-zA-Z1-9]+");

                outURL = "";

            }

            if (expr.contains("#[") & expr.contains("]")) {
                type = "splitarr";
                //outRule = ruleList.get(i).expr.replaceAll("\\#\\[", "[");
                outRule = ruleList.get(i).expr.replaceAll("#\\[", "").replaceAll("]", "").replaceAll(",", "|");
                outURL = "";
            }
            logger.info("APP type:  " + type + ",     outRule:" + outRule + ",      outURL:" + outURL);
            //System.out.println("APP type:  " + type + ",     outRule:" + outRule + ",      outURL:" + outURL);
            if (!outRule.equals("") && !outRule.contains("$") && !outRule.contains("#"))
                httpAPPExpressions.add(new Expression(outRule,
                        EnumSet.of(ExpressionFlag.SINGLEMATCH),
                        new RuleContext(ruleList.get(i).id, type, outURL, ruleList.get(i).rscId)));
            else
                logger.info("APP the rule " + expr + "   is not valid,please check..");
        }


        try {
            httpAPPDB = Database.compile(httpAPPExpressions);
        } catch (CompileErrorException compileErrorException) {
            logger.error("regex is not valid,please check:" + compileErrorException.getFailedExpression());
            return false;
        }

        httpAPPScanner = new Scanner();
        httpAPPScanner.allocScratch(httpAPPDB);


        return true;
    }

    private synchronized Boolean initHttpAPI(List<Rule> ruleList) {
        if (httpAPIExpressions.size() > 0)
            httpAPIExpressions.clear();

        if (httpAPIDB != null) {
            try {
                httpAPIDB.close();
            } catch (IOException ioException) {
                logger.error("httpAPIDB close fail,err msg:" + ioException.getMessage());
                return false;
            }
        }

        for (int i = 0; i < ruleList.size(); i++) {
            String expr = ruleList.get(i).expr.toLowerCase();

            String type = new String();
            String outRule = new String();
            String outURL = new String();
            String originalExpr = ruleList.get(i).expr;


            if (expr.contains("$")) {
                type = "mergeAPI";
                if (expr.contains("$[") && expr.contains("]")) {

                    outRule = ruleList.get(i).expr
                            .replaceAll("\\$\\[", "(")
                            .replaceAll("]", ")")
                            .replaceAll(",", "|");//[,,]--> (|||)
                    outURL = ruleList.get(i).expr.replaceAll("http://", "httpapi:http://");


                }
                if (expr.contains("$(string)"))//&& !expr.contains(":")
                {
                    if (!outRule.equals(""))
                        outRule = outRule.replaceAll("\\$\\(string\\)", "[a-zA-Z1-9]+");
                    else
                        outRule = ruleList.get(i).expr.replaceAll("\\$\\(string\\)", "[a-zA-Z1-9]+");
                    outURL = ruleList.get(i).expr.replaceAll("http://", "httpapi:http://");

                }

            }
            if ((expr.contains("#(string)") || expr.contains("#[")) && expr.contains("?")) {
                type = "splitAPI";


                if (expr.contains("#[") && expr.contains("]")) {
                    originalExpr = originalExpr
                            .replaceAll("#\\[", "#\"(")
                            .replaceAll("]", ")\"");
//                            .replaceAll(",", "|");//[,,]--> (|||)
                    int pos = originalExpr.indexOf("?");
                    String targetStr = "";
                    String prefixStr = "";
                    if (pos != -1) {
                        targetStr = originalExpr.substring(pos + 1);
                        prefixStr = originalExpr.substring(0, pos);
                    }

                    String originRegex = targetStr.replaceAll("([a-zA-Z1-9]+)\\.", "\"$1\":.")
                            .replaceAll("\\.?([a-zA-Z1-9]+)=#", ".\"$1\":=#")
                            .replaceAll("\\.", "\\\\[?{.*")
                            .replaceAll("=#\"", "\"");

                    //System.out.println("--------split API:---#[]  " + originRegex);

                    String[] arr = originRegex.split("&");

                    for (int j = 0; j < arr.length; j++) {
                        if (Pattern.matches("^\".*", arr[j]))
                            arr[j] = "\\[?{?.*" + arr[j];
                        else
                            arr[j] = arr[j].replaceAll("\\?\\{", "?{?");
                        //System.out.println("arr[j]" + arr[j]);
                    }

                    Combination.combinationStr = "";
                    Combination.arrangementSelect(arr, arr.length);
                    String regex = Combination.combinationStr;


                    outRule = prefixStr + "/" + regex.replaceFirst("(?s)(.*)" + "\\|", "$1")
                            .replaceAll("\\|", "|" + prefixStr + "/")
                            .replaceAll(",", "|");

                    outURL = ruleList.get(i).expr;//splitAPI save the original regex
                    //System.out.println("split #[],outRule:  " + outRule);

                }
                if (expr.contains("#(string)")) {
                    if (!outRule.equals("")) {
                        outRule = outRule.replaceAll("=#\\(string\\)", ".*");
                        //System.out.println("#[],#(string) process:" + outRule);
                    } else {

                        int pos = expr.indexOf("?");
                        String targetStr = "";
                        String prefixStr = "";
                        if (pos != -1) {
                            targetStr = expr.substring(pos + 1);
                            prefixStr = expr.substring(0, pos);
                        }

                        String originRegex = targetStr.replaceAll("([a-zA-Z1-9]+)\\.", "\"$1\":.")
                                .replaceAll("\\.?([a-zA-Z1-9]+)=#", ".\"$1\":=#")
                                .replaceAll("\\.", "\\\\[?{.*")
                                .replaceAll("=#\\(string\\)", ".*");
                        String[] arr = originRegex.split("&");

                        for (int j = 0; j < arr.length; j++) {
                            if (Pattern.matches("^\".*", arr[j]))
                                arr[j] = "\\[?{?.*" + arr[j];
                            else
                                arr[j] = arr[j].replaceAll("\\?\\{", "?{?");
                        }

                        Combination.combinationStr = "";
                        Combination.arrangementSelect(arr, arr.length);
                        String regex = Combination.combinationStr;


                        outRule = prefixStr + "/" + regex.replaceFirst("(?s)(.*)" + "\\|", "$1").replaceAll("\\|", "|" + prefixStr + "/");

                        outURL = ruleList.get(i).expr;//splitAPI save the original regex
                    }

                }
            }

            logger.info("API type:  " + type + ",     outRule:" + outRule + ",      outURL:" + outURL);
            //System.out.println("API type:  " + type + ",     outRule:" + outRule + ",      outURL:" + outURL);
            if (!outRule.equals("") && !outRule.contains("$") && !outRule.contains("#"))
                httpAPIExpressions.add(new Expression(outRule,
                        EnumSet.of(ExpressionFlag.SINGLEMATCH),
                        new RuleContext(ruleList.get(i).id, type, outURL, ruleList.get(i).rscId)));
            else
                logger.info("API the rule " + expr + "   is not valid,please check..");
        }

        try {
            httpAPIDB = Database.compile(httpAPIExpressions);
        } catch (CompileErrorException compileErrorException) {
            logger.error("regex is not valid,please check:" + compileErrorException.getFailedExpression());
            return false;
        }
        httpAPIScanner = new Scanner();
        httpAPIScanner.allocScratch(httpAPIDB);
        return true;


    }

    public String scanHttpAPP(String url, boolean isDesc, StringBuilder rscId) {
        List<Match> matchList = scanHttp(url);
        try {
            if (matchList.isEmpty() || matchList == null) {
                logger.info("no match APP rules");
                String tsar = url.replace("http://", "").replace("https://", "");
                String[] arr = tsar.split("/");
                String str = arr[0];
                if (str.indexOf(":") != -1)
                    str = str.substring(0, str.indexOf(":"));
                return "httpapp:" + str;
            }

        } catch (NullPointerException nu) {
            return "";
        }


        int index = getIndex(matchList, isDesc);
        RuleContext ruleContext = (RuleContext) matchList.get(index).getMatchedExpression().getContext();
        if (rscId != null) {
            rscId.delete(0, rscId.length()).append(ruleContext.getRscId());
        }
        return ruleContext.getOutURL();

    }

    private synchronized List<Match> scanHttp(String input) {

        if (httpAPPDB == null) {
            logger.error("db init failed,please check init func.");
            LinkedList<Match> matches = new LinkedList<Match>();
            return matches;
        }

        List<Match> matches = httpAPPScanner.scan(httpAPPDB, input);//new matches in function

        for (Match match : matches) {
            RuleContext ruleContext = (RuleContext) match.getMatchedExpression().getContext();
            if (ruleContext.getType().equals("splitarr")) {
                String regex = match.getMatchedExpression().getExpression();
                regex = regex.replaceAll(",", "|").replaceAll("\\[", "").replaceAll("]", "");
                ruleContext.setOutURL(matchUrl(regex, input));
            }
            if (ruleContext.getType().equals("split")) {

                String rewriteRegex = match.getMatchedExpression().getExpression().replaceAll("a-zA-Z1-9", "^/");
                String matched = matchUrl(rewriteRegex, input);

                ruleContext.setOutURL(matched);
            }

            ruleContext.setOutURL(ruleContext.getOutURL().replaceAll("http://", "httpapp:"));

            logger.info("APP match rule id:" + ruleContext.getId());
        }
        return matches;


    }

    public String scanHttpAPI(String url, String method, String jsonString, boolean isDesc, StringBuilder rscId) {
        List<Match> matchList = scanAPI(url, method, jsonString);
        if (matchList.isEmpty() || matchList == null) {
            logger.info("no match API rules.....");
            String noMatchedUrl = "httpapi:" + url + "#" + method;
            return noMatchedUrl;
        }


        int index = getIndex(matchList, isDesc);
        RuleContext ruleContext = (RuleContext) matchList.get(index).getMatchedExpression().getContext();
        if (rscId != null) {
            rscId.delete(0, rscId.length()).append(ruleContext.getRscId());
        }
        if (matchList != null)
            matchList.clear();
        return ruleContext.getOutURL();

    }


    private synchronized List<Match> scanAPI(String url, String method, String jsonString) {
        if (httpAPIDB == null) {
            logger.error("db init failed,please check init func.");
            LinkedList<Match> matches = new LinkedList<Match>();
            return matches;
        }


        String scanStr = "";
        if (jsonString == null || jsonString.equals(""))
            scanStr = url;
        else
            scanStr = url + "/" + jsonString;
        logger.debug("API scan str:" + scanStr);


        List<Match> matches = httpAPIScanner.scan(httpAPIDB, scanStr);//new instance in function

        for (Match match : matches) {
            RuleContext ruleContext = (RuleContext) match.getMatchedExpression().getContext();
            if (ruleContext.getType().equals("splitAPI")) {
                String notModifiedURL = ruleContext.getNotModifiedURL();
                ruleContext.setOutURL("httpapi:" + matchedUrl(notModifiedURL, method, jsonString));
            }
            if (ruleContext.getType().equals("mergeAPI")) {
                ruleContext.setOutURL(ruleContext.getNotModifiedURL() + "#" + method);

            }

            logger.info("API match rule id:" + ruleContext.getId());
        }
        return matches;

    }

    private int getIndex(List<Match> matchList, boolean isDesc) {
        int indexMax = 0;
        int indexMin = 0;
        int max = 0;
        int min = 0;

        for (int i = 0; i < matchList.size(); i++) {
            RuleContext ruleContext = (RuleContext) matchList.get(i).getMatchedExpression().getContext();
            if (ruleContext.getId() > max) {
                max = ruleContext.getId();
                indexMax = i;
            }
            if (ruleContext.getId() < min | min == 0) {
                min = ruleContext.getId();
                indexMin = i;
            }
        }
        if (isDesc)
            return indexMax;
        else
            return indexMin;
    }

//    /**
//     * @param ruleString    http://tb.com/query?name=#(string)&users.name=#(string)
//     * @param method        GET/POST
//     * @param urlJsonString http://tb.com/query/jsonstring
//     * @return
//     */
//    private String matchedUrl(String ruleString, String method, String urlJsonString) {
//        String output = new String();
//        int index = ruleString.indexOf("?");
//        output += ruleString.substring(0, index) + "#" + method;
//
//        LinkedHashMap<String, String> originalModifiedMap = new LinkedHashMap<String, String>();
//        if (index > 0) {
//            String postfixString = ruleString.substring(index + 1);
//            String[] arr = postfixString.split("&");
//            for (int i = 0; i < arr.length; i++) {
//                String originalRule = arr[i];
//
//
//                String modifiedString = originalRule.replaceAll("([a-zA-Z1-9]+)\\.", "\"$1\":.")
//                        .replaceAll("\\.?([a-zA-Z1-9]+)=#", ".\"$1\":=#")
//                        .replaceAll("\\.", "\\\\[?\\\\{.*")
//                        .replaceAll("=#\\(string\\)", "\"([a-zA-Z1-9]+)\"");
//                modifiedString = ".*" + modifiedString + ".*";
//                String value = urlJsonString.replaceAll(modifiedString, "$1");
//                String standardOut = originalRule.replaceAll("(.*)=", method + ".$1").replaceAll("#\\(string\\)", "=" + value);
//                output = output + ";" + standardOut;
//
//
//            }
//            return output;
//        }
//
//        return "";
//    }

    /**
     * @param ruleString    http://tb.com/query?name=#(string)&users.name=#(string)
     * @param method        GET/POST
     * @param urlJsonString http://tb.com/query/jsonstring
     * @return
     */
    private String matchedUrl(String ruleString, String method, String urlJsonString) {
        String output = new String();
        int index = ruleString.indexOf("?");
        output += ruleString.substring(0, index) + "#" + method;

        LinkedHashMap<String, String> originalModifiedMap = new LinkedHashMap<String, String>();
        if (index > 0) {
            String postfixString = ruleString.substring(index + 1);
            String[] arr = postfixString.split("&");
            for (int i = 0; i < arr.length; i++) {
                String originalRule = arr[i];
                String modifiedString = originalRule.replaceAll("([a-zA-Z1-9]+)\\.", "\"$1\":.")
                        .replaceAll("\\.?([a-zA-Z1-9]+)=#", ".\"$1\":=#")
                        .replaceAll("\\.", "\\\\[?\\\\{.*")
                        .replaceAll("=#\\(string\\)", "\"([a-zA-Z1-9]+)\"")
                        .replaceAll("=#\\[","\"(")
                        .replaceAll(",","|")
                        .replaceAll("]$",")\"");
                modifiedString = ".*" + modifiedString + ".*";
                //System.out.println("matchedUrl -----------modifiedString:----"+modifiedString);
                String value = urlJsonString.replaceAll(modifiedString, "$1");
                //System.out.println("matchedUrl----value:"+value);
                String standardOut = originalRule.replaceAll("(.*)=", method + ".$1")
                        .replaceAll("#\\(string\\)", "=" + value)
                        .replaceAll("#\\[.*\\]","="+value);
                //System.out.println("matchedUrl----------standardOut-------------"+standardOut);
                output = output + ";" + standardOut;


            }
            return output;
        }

        return "";
    }


    public ConstructRegex(boolean isYaml) {

        Yaml yaml = new Yaml(new Constructor(LabelRule.class));

        InputStream inputStreamAPP = this.getClass().getClassLoader().getResourceAsStream("httpapp.yaml");
        labelAPPRule = yaml.load(inputStreamAPP);

        InputStream inputStreamAPI = this.getClass().getClassLoader().getResourceAsStream("httpapi.yaml");
        labelAPIRule = yaml.load(inputStreamAPI);


    }

    public ConstructRegex() {
    }


    private String matchUrl(String regex, String input) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        if (matcher.find()) {
            return matcher.group(0).trim();
        }
        return "";

    }

}
